"""
-*- coding: utf-8 -*-
@Author  : Link
@Time    : 2022/5/9 17:44
@Software: PyCharm
@File    : ui_file_load_widget.py
@Remark  : 
"""
import os
import time

from PySide2.QtGui import QColor, QGuiApplication
from PySide2.QtWidgets import QWidget, QHeaderView, QFileDialog, QTableWidgetItem
from PySide2.QtCore import Qt, QThread, Signal, Slot

from typing import List, Set, Dict, Union
import pandas as pd

from ui_component.ui_to_analysis_stdf.ui_designer.ui_file_load import Ui_Form as FileLoadForm
from workspace_core.core_to_analysis_stdf.semi_stdf import SemiStdfUtils
from workspace_core.core_to_analysis_stdf.stdf_variable import GlobalVariable
from workspace_core.stdf_core.stdf_to_csv import LinkStdf


class RunStdfAnalysis(QThread):
    dataSignal = Signal(pd.DataFrame, dict, dict, pd.DataFrame)
    indexSignal = Signal(dict)  # index=int, status=int, message=str : -1:DLL解析失败, 1:解析成功, 0:running
    stdf = None  # type:LinkStdf
    file_list = None  # type:List[dict]
    id = 0
    cache_pro = None  # type:Union[str, None]
    limit_dict = None  # type:Dict[str, GlobalVariable.LimitClass]  # 只能传出一个limit DICT

    def __init__(self, parent=None):
        super(RunStdfAnalysis, self).__init__()
        self.parent = parent
        self.stdf = LinkStdf()
        self.stdf.init()

    def set_analysis_list(self, file_list):
        self.file_list = file_list

    def set_id(self, mid_nm):
        self.id = int(mid_nm * 1E5)

    def read_csv(self, mdi_id: int, csv_file: str, is_r: bool, mode: int) -> (bool, pd.DataFrame, list):
        """
        解析dll生成的csv文件
        :param mdi_id:
        :param mode: 选取的PART_FLAG, 新版本改为了每个文件都要确认
        :param csv_file: 5行12列前为info, 5行12列后为limit和unit
        :param is_r: 是否是重测, 是最后一次复测的话就勾选上, 获取到Fail Index信息
        :return:
        TODO: 可以添加一个 Limit.csv 专门用于PAT
              可以考虑将每个批次的Limit再放一个List[Dict]来做一个存储
        """
        info = pd.read_csv(csv_file, nrows=7)
        data = pd.read_csv(csv_file, skiprows=8)
        "======== Sample ======="
        if not is_r:
            data = data[data["FAIL_FLAG"] == 1]
        if mode == 3:
            " 如果没有复测的话, 就不处理 "
            if len(data[data["PART_FLAG"] & 0b11 == 2]) != 0:
                " 先读取FIRST, 即 & 0b11 = 0得PASS, 然后 0b11 == 2得所有"
                data_first_pass = data[(data["PART_FLAG"] & 0b11 == 0) & (data["FAIL_FLAG"] == 1)]
                data = pd.concat([data_first_pass, data[data["PART_FLAG"] & 0b11 == 2]])
        if mode == 1:
            " 读取FIRST, 即 & 0b11 = 0"
            data = data[data["PART_FLAG"] & 0b11 == 0]
        if mode == 2:
            data = data[data["PART_FLAG"] & 0b11 == 2]
        temp_limit = []
        for index, test_id in enumerate(info.columns[12:]):
            if int(info[test_id][0]) == 0:
                continue
            # 如果是nan的数据名称, 那么数据的类型则为float
            text = "{}:{}".format(int(info[test_id][0]), info[test_id][1] if isinstance(info[test_id][1], str) else "")
            test_text = text
            test_num = int(info[test_id][0])
            l_limit = float(info[test_id][2])
            h_limit = float(info[test_id][3])
            unit = info[test_id][4]
            test_sort = index
            l_limit_type = info[test_id][5]
            h_limit_type = info[test_id][6]
            temp_limit.append({
                "ID": mdi_id,
                "SORT": test_sort,
                "TEST_TEXT": text,
                "LO_LIMIT": l_limit,
                "HI_LIMIT": h_limit,
                "UNITS": unit,
            })
            self.limit_dict[text] = GlobalVariable.LimitClass(test_text, test_num, l_limit, h_limit, unit, test_sort,
                                                              l_limit_type, h_limit_type)

        data["SITE_NUM"] = data["SITE_NUM"].apply(lambda x: 'S{:0>3d}'.format(x))

        qty = len(data)
        pass_qty = len(data[data["FAIL_FLAG"] == 1])
        if qty == 0:
            pass_yield = "0.0%"
        else:
            pass_yield = '{}%'.format(round(pass_qty / qty * 100, 2))
        info_dict = {
            'ID': mdi_id,
            'R': 'Y' if is_r else 'N',
            'PART_FLAG': GlobalVariable.PART_FLAGS[mode],
            'LOT_ID': info["LOT_ID"][0],
            'SB_LOT_ID': info["LOT_ID"][2],
            'WAFER_ID': info["LOT_ID"][4],
            'SETUP_TIME': int(info["SETUP_TIME"][0]),
            'START_TIME': int(info["START_TIME"][0]),
            'FINISH_TIME': int(info["FINISH_TIME"][0]),
            'FLOW_ID': info["FLOW_ID"][0],
            'PART_TYPE': info["SETUP_TIME"][2],
            'JOB_NAME': info["STAT_NUM"][2],
            'TEMPERATURE': info["FLOW_ID"][2],
            'QTY': qty,
            'PASS': pass_qty,
            'YIELD': pass_yield,
        }
        data.insert(loc=0, column='ID', value=mdi_id)
        return info_dict, data, temp_limit

    def run(self) -> None:
        if self.file_list is None:
            return
        df_dict, self.cache_pro, self.limit_dict, by_analysis_list, all_limit_list = {}, None, dict(), [], []
        for index, each in enumerate(self.file_list):
            # try:
            """ 阻止不同的程序一起解析 """
            # if self.cache_pro is not None and self.cache_pro != each['JOB_NAME']:
            #     self.indexSignal.emit({"index": index, "status": -1, "message": "异同的测试程序!暂无法解析不同程序数据!"})
            #     continue
            start = time.perf_counter()
            stdf_path = each["FILEPATH"]
            part_flag = each["PART_FLAG"]
            read_fail = each["READ_FAIL"]
            _, stdf_name = os.path.split(stdf_path)
            stdf_name = stdf_name[:stdf_name.rfind('.')]
            save_path = os.path.join(GlobalVariable.CACHE_PATH, each["LOT_ID"])
            if not os.path.exists(save_path):
                os.mkdir(save_path)
            save_name = os.path.join(save_path, stdf_name + '.csv')
            if not os.path.exists(save_name):
                self.indexSignal.emit({"index": index, "status": 0, "message": "开始解析STDF中!"})
                boolean = self.stdf.exec_stdf_to_csv_rawdata(stdf_path, save_name)
                if not boolean:
                    os.remove(save_name)
                    self.indexSignal.emit({"index": index, "status": -1, "message": "STDF文件解析失败!"})
                    continue
            else:
                self.indexSignal.emit({"index": index, "status": 0, "message": "缓存文件存在,调用缓存数据!"})
            """
            开始读取csv然后进行数据处理!
            """
            mdi_id = int(self.id + index)
            by_analysis_data_dict, df, limit_list = self.read_csv(
                mdi_id=mdi_id, csv_file=save_name, is_r=read_fail, mode=part_flag)
            by_analysis_data_dict["FILE_NAME"] = stdf_path
            """ 阻止不同的程序一起解析 """
            if self.cache_pro is None:
                self.cache_pro = by_analysis_data_dict['JOB_NAME']
            all_limit_list = all_limit_list + limit_list
            df_dict[mdi_id] = df
            by_analysis_list.append(by_analysis_data_dict)
            use_time = round(time.perf_counter() - start, 2)
            self.indexSignal.emit({"index": index, "status": 1, "message": "STDF解析文件成功!用时{}s".format(use_time)})
        """
        数据整理OK
        """
        limit_df = pd.DataFrame(all_limit_list)
        summary_df = pd.DataFrame(by_analysis_list)
        self.dataSignal.emit(summary_df, self.limit_dict, df_dict, limit_df)
        self.indexSignal.emit({"index": len(self.file_list), "status": 11, "message": "数据解析完成"})


class FileLoadWidget(QWidget, FileLoadForm):
    """
    file select
    STDF文件 Load模块,需要使用一种线程池来解析STDF
    建议的做法, 为了节省内存, 做懒处理, FileLoad只解析, 然后提取关键数据给Tree, 减少内存的占用
    缺点就是载入的数据变长了
    """

    dataSignal = Signal(tuple)  # 传送父子数据给TreeWidget
    closeSignal = Signal(int)
    messageSignal = Signal(str)  # 追加文本信息

    th = None  # type:RunStdfAnalysis
    select_file = None  # type:Union[Set[str], None]

    have_cache_data = False
    cache_data = None
    parent = None

    def __init__(self, parent=None, space_nm=1):
        super(FileLoadWidget, self).__init__(parent)
        self.setupUi(self)
        self.parent = parent
        self.setWindowTitle("STDF File Select")
        self.space_nm = space_nm
        self.comboBox.addItems(GlobalVariable.PART_FLAGS)
        self.title = "STDF数据载入空间: {}".format(space_nm)
        self.th = RunStdfAnalysis(self)
        self.th.set_id(self.space_nm)
        self.th.dataSignal.connect(self.th_data_event)
        self.th.indexSignal.connect(self.th_message_event)
        self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground)
        self.tableWidget.set_table_head(GlobalVariable.FILE_TABLE_HEAD)
        self.tableWidget.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeToContents)

    def select_stdf(self) -> List[str]:
        """
        去重复, 读取STDF
        :return:
        """
        path_list, _ = QFileDialog.getOpenFileNames(self,
                                                    'Open Stdf File',
                                                    filter='stdf(*.std;*.stdf;*.stdtemp)',
                                                    # options=QFileDialog.DontUseNativeDialog,
                                                    )
        return path_list

    def select_stdf_directory(self) -> List[str]:
        """
        从整个文件夹下选取所有的文件
        """
        path_list = []
        directory = QFileDialog.getExistingDirectory(self, "getExistingDirectory", "./")
        for root, dirs, files in os.walk(directory):
            for file in files:
                suffix = os.path.splitext(file)[-1]
                if suffix in {".std", ".stdf", ".stdtemp"}:
                    path_list.append(os.path.join(root, file))
        return path_list

    def first_select(self):
        """
        初始化选择
        :return:
        """
        self.select_file = set()
        select_stdf = self.select_stdf()
        if len(select_stdf) == 0:
            return self.parent.message_show("无文件被选取!")
        self.select_file = set(select_stdf)
        self.analysis_path_stdf_by_semi_ate()

    def first_directory_select(self):
        """
        初始化文件夹下文件选择
        """
        self.select_file = set()
        select_stdf = self.select_stdf_directory()
        if len(select_stdf) == 0:
            return self.parent.message_show("无文件被选取!")
        self.select_file = set(select_stdf)
        self.analysis_path_stdf_by_semi_ate()

    def analysis_path_stdf_by_semi_ate(self):
        """
        执行数据载入, 并按照时间排序
        :return:
        """
        if not self.select_file:
            return self.parent.message_show("无文件被选取, 无法执行分析!")
        table_data = []
        for filepath in self.select_file:
            table_data.append(SemiStdfUtils.get_mir_by_by_semi_ate(filepath))
        table_data = sorted(table_data, key=lambda ev: ev['START_TIME'])
        self.tableWidget.set_table_data(table_data)
        self.progressBar.setValue(0)
        self.progressBar.setMaximum(self.tableWidget.table_count)

    @Slot()
    def on_pushButton_2_pressed(self):
        """
        追加选择数据
        :return:
        """
        select_stdf = self.select_stdf()
        if len(select_stdf) == 0:
            return self.parent.message_show("无文件被选取!")
        if set(select_stdf) == self.select_file:
            return self.parent.message_show("文件结构未改变!")
        self.have_cache_data = False
        self.select_file = self.select_file | set(select_stdf)
        self.analysis_path_stdf_by_semi_ate()

    @Slot()
    def on_pushButton_3_pressed(self):
        self.have_cache_data = False
        self.tableWidget.set_read_all_r(Qt.Checked)

    @Slot()
    def on_pushButton_4_pressed(self):
        self.have_cache_data = False
        self.tableWidget.set_read_all_r(Qt.Unchecked)

    @Slot()
    def on_pushButton_5_pressed(self):
        self.have_cache_data = False
        self.on_pushButton_pressed()

    @Slot()
    def on_pushButton_6_pressed(self):
        self.have_cache_data = False
        self.select_file = set()
        self.tableWidget.clearContents()

    @Slot(int)
    def on_comboBox_currentIndexChanged(self, e):
        self.have_cache_data = False
        self.tableWidget.set_all_part_flag(e)

    @Slot(dict)
    def th_message_event(self, info: dict):
        index = info['index']
        self.progressBar.setValue(index)
        if index == self.tableWidget.table_count:
            return self.parent.message_show("{} > 完成数据解析!".format(self.title))
        # self.tableWidget.selectRow(index)
        item = QTableWidgetItem(info['message'])
        if info['status'] == -1:
            item.setBackground(QColor(255, 110, 55))
        elif info['status'] == 0:
            item.setBackground(QColor(180, 208, 201))
        else:
            item.setBackground(QColor(176, 255, 210))
        self.tableWidget.update_table_data(index, "MESSAGE", item)

    @Slot(pd.DataFrame, dict, dict, pd.DataFrame)
    def th_data_event(self, df: pd.DataFrame, limit_dict: dict, df_dict: dict, limit_df: pd.DataFrame):
        """
        接收后台整理好的数据到前台调用
        :return:
        """
        if not isinstance(df, pd.DataFrame) and len(df) == 0:
            return self.parent.message_show("数据解析失败!")
        self.cache_data = (df, limit_dict, df_dict, limit_df)
        self.dataSignal.emit(self.cache_data)
        self.have_cache_data = True
        self.messageSignal.emit(self.title + ">传入数据成功!")
        self.pushButton.setEnabled(True)

    @Slot()
    def on_pushButton_pressed(self):
        """
        想要RUN之前得进行一些判定
        使用线程池进行数据处理
        可以给前台传一个Process
        给线程传入R, R版本才会分析Fail项目
        :return:
        """
        if self.tableWidget.table_count == 0:
            return self.parent.message_show("无文件结构被读取")

        if self.have_cache_data is True and self.cache_data is not None:
            """
            直接将缓存数据传出去就好了
            """
            self.messageSignal.emit(self.title + ">传入数据")
            self.dataSignal.emit(self.cache_data)
            return

        self.tableWidget.update_temp_data()
        self.th.set_analysis_list(self.tableWidget.temp_table_data)
        self.th.start()
        self.pushButton.setEnabled(False)

    def keyPressEvent(self, event):
        """ Ctrl + C复制表格内容 """
        if event.modifiers() == Qt.ControlModifier and event.key() == Qt.Key_C:
            # 获取表格的选中行
            ranges = self.tableWidget.selectedRanges()[0]
            if not ranges:
                return
            selected_ranges = ranges[0]  # 只取第一个数据块,其他的如果需要要做遍历,简单功能就不写得那么复杂了
            text_str = "\t".join(GlobalVariable.FILE_TABLE_HEAD) + '\n'  # 最后总的内容
            # 行（选中的行信息读取）
            for row in range(selected_ranges.topRow(), selected_ranges.bottomRow() + 1):
                row_str = ""
                # 列（选中的列信息读取）
                for col in range(selected_ranges.leftColumn(), selected_ranges.rightColumn() + 1):
                    item = self.tableWidget.item(row, col)
                    if item is None:
                        row_str += '\t'
                        continue
                    row_str += item.text() + '\t'  # 制表符间隔数据
                text_str += row_str + '\n'  # 换行
            clipboard = QGuiApplication.clipboard()  # 获取剪贴板
            clipboard.setText(text_str)  # 内容写入剪贴板


if __name__ == '__main__':
    import sys
    from PySide2.QtWidgets import QMainWindow, QApplication

    app = QApplication(sys.argv)
    win = FileLoadWidget()
    win.show()
    win.first_select()
    sys.exit(app.exec_())
